package risoe.syslab.fhnew.core;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.rmi.RemoteException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import risoe.syslab.fhnew.config.PlatformConfig;
import risoe.syslab.fhnew.controller.ControllerManager;
import risoe.syslab.fhnew.db.LoggerManager;
import risoe.syslab.fhnew.hw.HWModule;
import risoe.syslab.fhnew.hw.ProcessImage;
import risoe.syslab.util.NanoHTTPD;

/**
 * Replacement for the application server. Starts up the whole platform.
 * @author olge
 */
public class FlexhouseCore extends NanoHTTPD {
	private static final int JSON_PORT=8091;
	private static final String CMDPARM="cmd";
	private static final boolean LOG_CONSOLE = false;

	private ControllerManager controllerMgr;
	private ProcessImage procImg;
	private HWModule hwModule;
	private LoggerManager loggerMgr;
	
	// Parameters specified in platform.xml
	PlatformConfig mPlatformConfig;

	public FlexhouseCore(int port, String cfgfile) throws IOException {
		super(port);

		// Parse the parameters defined in platform.xml
		mPlatformConfig = new PlatformConfig(cfgfile);
		String buildingcfgfile = mPlatformConfig.getParameter(PlatformConfig.BUILDING_CONFIG_KEY, 
				"building.xml."+mPlatformConfig.getHostName());

		System.out.println("Initializing LoggerManager");
		loggerMgr = new LoggerManager(mPlatformConfig);

		System.out.println("Starting HWModule");
		hwModule=new HWModule(buildingcfgfile, loggerMgr);
		System.out.println("Hardware Interfaces are ready");

		// Get the ProcessImage
		procImg = hwModule.getProcessImage();

		// Start the controllermanager
		try {
			controllerMgr = new ControllerManager(procImg, hwModule.getBuildingConfig(), loggerMgr, mPlatformConfig);
			System.out.println("ControllerManager started");
		} catch (RemoteException e) {
			// RMI Failed
			System.out.println("RMI Initialization for ControllerManager failed. Exiting now...");
			//TODO Make the platform resistant to RMI initialization failure (Which should never happen)
			System.exit(1);
		}

	}

	private void shutdown() {
		procImg.shutdown();
		hwModule.stopHWThreads();
		if (controllerMgr != null){
			controllerMgr.shutdown();
		}
		loggerMgr.getPlatformEventLogger().logEvent("Core", 
				"NOTICE", "-------------- PLATFORM STOPPED -------------- ");
		loggerMgr.stopLoggers();
	}

	/**
	 * Logs to the standard console and to a text file
	 * @author M. Svendsen
	 */
	public static class ConsoleLogStream extends PrintStream {
		private final PrintStream mFileStream;
		private final SimpleDateFormat mFormat = new SimpleDateFormat("dd-MM-yyyy-HH:mm:ss");
		private Date mDate = new Date();

		public ConsoleLogStream(OutputStream consoleStream, PrintStream fileStream) {
			super(consoleStream);
			this.mFileStream = fileStream;
		}

		@Override
		public void close() {
			super.close();
		}

		@Override
		public void flush() {
			super.flush();
			mFileStream.flush();
		}

		@Override
		public void print(String s) {
			// TODO Auto-generated method stub
			mDate.setTime(System.currentTimeMillis());
			super.print(s);
			mFileStream.print(mFormat.format(mDate)+"   "+s);
			mFileStream.println();
		}
	}

	//Emulates the JSON interface in the SYSLAB application server, in order to allow remote monitoring by Nagios
	//"Applications" in the Flexhouse platform are hardcoded right now and include:
	//- LoggerManager
	//- HWModule
	//- ControllerManager

	private static final int NUM_APPS=3;
	private static final String[] APP_NAMES={"LoggerManager","HardwareModule","ControllerManager"};
	private static final boolean[] APP_AUTOSTART={true,true,true};

	public Response serve( String uri, String method, Properties header, Properties parms ) {
		boolean error=false;
		boolean forbidden=false;
		boolean notfound=false;
		String cmdparm=parms.getProperty(CMDPARM);

		if ("json".equalsIgnoreCase(cmdparm)) {
			StringBuilder msg = new StringBuilder();

			int numApps = NUM_APPS; //hardcoded right now, maybe make this dynamic later
			msg.append( "[" );
			for (int i=0;i<numApps;i++) {
				MonitoredModule mm=null;
				switch(i) {
				case 0:
					mm=loggerMgr;
					break;
				case 1:
					mm=hwModule;
					break;
				case 2:
					mm=controllerMgr;
					break;
				}
				MonitoredModule.Status status=MonitoredModule.Status.Fault;
				String fault="Module '"+APP_NAMES[i]+"' is null.";
				if (mm!=null) {
					status = mm.getStatus();
					fault=escape(mm.getFaultMessage());
				}
				if (i>0) msg.append(", ");
				msg.append( "{ " );
				msg.append( "\"name\": \"" + escape(APP_NAMES[i]) + "\"" );
				msg.append( ", \"status\": \"" + status + "\"");
				msg.append( ", \"autostart\": \"" + APP_AUTOSTART[i] + "\"");
				msg.append( ", \"error\": \"" + fault + "\"" );
				msg.append( "}" );
			}
			msg.append( "]" );
			return new Response( HTTP_OK, MIME_HTML, msg.toString() );
		}
		else {
			notfound=true;
		}

		if (error) {    
			return new Response( HTTP_INTERNALERROR, MIME_PLAINTEXT,
					"Error" );
		}
		if (forbidden) {
			return new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT,
					"FORBIDDEN: Won't serve for security reasons." );
		}
		if (notfound) {
			return new Response( HTTP_NOTFOUND, MIME_PLAINTEXT,
					"Error 404, not found." );
		}
		return null;

	}



	private String escape(String msg) {
		msg = msg.replace("\\", "\\\\");
		msg = msg.replace("\"", "\\\"");
		msg = msg.replace("\n", "\\n");
		return msg;
	}


	/**
	 * Main entrypoint of the entire flexhouse code
	 * @throws FileNotFoundException 
	 */
	public static void main(String args[]) {
		if (LOG_CONSOLE){
			// Pipe stdout and stderr to files aswell as to the standard console
			String identifier = new SimpleDateFormat("dd-MM-yyyy--HH-mm").format(new Date());
			try {
				System.setErr(new ConsoleLogStream(System.err, new PrintStream(new File("tmp/Error_"+ identifier +".txt"))));
				System.setOut(new ConsoleLogStream(System.out, new PrintStream(new File("tmp/Out_"+ identifier +".txt"))));
			}
			catch (IOException e) {
				System.out.println("Could not open console log output files. Shutting down.");
				System.exit(1);
			}
		}

		try {

			String configFile = args.length > 0? args[0]: null;
			final FlexhouseCore core=new FlexhouseCore(JSON_PORT, configFile);

			// Add a shutdownhook to properly shut down the threads in the processimage
			Runtime.getRuntime().addShutdownHook(new Thread() {
				public void run() {
					System.out.println("Shutting Down PowerFlexHouse Platform!");
					if (core!=null) {
						core.shutdown();
					}
				}
			});
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}
}
